google rpc | test google express rpc handler | Cell 4 | Search

This code provides a framework for creating cloud functions by defining a generic handler and a function to generate specialized handlers from code snippets.

Run example

npm run import -- "generic gcloud function handler"

generic gcloud function handler

var importer = require('../Core')
var {selectAst} = importer.import("select ast")
var {
    niceName, getExports, getParameters
} = importer.import("nice name",
"get exports",
"get parameters")

async function handler(req, res) {
    if(!req && process.stdout.isTTY) {
        res = {}
        req = {query: {}, body: {}, form: {}}
        Array.from(process.argv).forEach(arg => {
            req.query[arg.split('=')[0]] = arg.split('=').slice(1).join('=')
        })
    }
    res.set('Access-Control-Allow-Origin', '*')
    var parameters = Object.values(req.query || {})
        .concat(Object.values(req.body || {}))
        .concat(Object.values(req.form || {}))
    
    var func = require('./entry.js')
    return await func(...parameters)
        .then(r => !res ? console.log(r) : res.status(200).send(r))
        .catch(e => !res ? console.log(e) : res.status(500).send(e))
}

// use syntax to alter the function handler above
//  to pull out parameters and call the requested function
//  slightly simpler code than including the entire library
function makeHandler(entry) {
    var thisCell = importer.interpret('generic cloud function handler')
    if(!Array.isArray(entry)) {
        entry = [entry]
    }
    var exports = []
    var interpret = importer.interpret(entry)
    var handlers = interpret.map(cell => {
        // replace the require statement
        // TODO: make this part generic, moving vars to parameters
        var doc = selectAst('.', handler.toString())

        var exportsName = getExports(cell.code)[0]
        exports.push(exportsName + 'Handler')
        var functionStmt = selectAst(`
//FunctionDeclaration/Identifier[@name="handler"]`, doc)
        functionStmt.setAttribute('name', exportsName + 'Handler')
        var parameters = getParameters(cell.code).slice(1)

        // replace the parameters with names
        var replaceParams = selectAst('//VariableDeclaration', `
var parameters = [${parameters
            .map(p => `(req.body || {})['${p}'] || (req.form || {})['${p}'] || (req.query || {})['${p}']`)
            .join(',\n')}]`)
        
        var assignmentStmt = selectAst([`//VariableDeclaration[./*/*[@name = "parameters"]]`], doc)
        assignmentStmt[0].replaceWith(replaceParams)
        
        var requireStmt = selectAst(`//VariableDeclaration[./*/*[@name = "func"]]`, doc)
        var replaceRequire = selectAst('//VariableDeclaration', `
var ${exportsName} = require('./${niceName(cell)}')`)
        requireStmt.replaceWith(replaceRequire)
                
        // replace function call with new named
        var callStmt = selectAst(`//CallExpression/Identifier[@name = "func"]`, doc)
        callStmt.setAttribute('name', exportsName)
        return doc.ownerDocument.toString()
    }).join('\n\n')
    return `${handlers}

module.exports = {
    ${exports.join(',\n    ')}
}`
}

module.exports = {
    makeHandler,
    handler
}

What the code could have been:

const importer = require('../Core');

const { selectAst, niceName, getExports, getParameters } = importer.import([
 'select ast',
  'nice name',
  'get exports',
  'get parameters',
]);

async function handler(req, res) {
  if (!req && process.stdout.isTTY) {
    req = { query: {}, body: {}, form: {} };
    Object.assign(req.query, process.argv.slice(2).reduce((acc, arg) => {
      const [key, value] = arg.split('=');
      acc[key] = value;
      return acc;
    }, {}));
  }
  res.set('Access-Control-Allow-Origin', '*');
  const query = Object.values(req.query || {});
  const body = Object.values(req.body || {});
  const form = Object.values(req.form || {});
  const parameters = [...query,...body,...form];

  const entry = require('./entry.js');
  return await entry(...parameters)
   .then((r) => res.status(200).send(r))
   .catch((e) => res.status(500).send(e));
}

function makeHandler(entry) {
  if (!Array.isArray(entry)) {
    entry = [entry];
  }
  const handlers = entry.map((cell) => {
    const exportsName = getExports(cell.code)[0];
    const funcName = niceName(cell);
    const parameters = getParameters(cell.code).slice(1);
    const reqAccess = `req.${parameters.map((p) => p).join('.')}`;
    const funcCall = `require('./${funcName}')(${parameters.map((p) => reqAccess).join(', ')})`;
    const newFunc = `
function ${exportsName}Handler(req, res) {
  try {
    return ${funcCall}
     .then((r) => res.status(200).send(r))
     .catch((e) => res.status(500).send(e));
  } catch (e) {
    console.error(e);
    res.status(500).send(e);
  }
}`;

    return newFunc;
  }).join('\n\n');
  return `${handlers}
module.exports = {
  ${Object.keys(entry)
   .map((cell) => getExports(cell.code)[0] + 'Handler')
   .join(',\n  ')},
}`;
}

module.exports = {
  makeHandler,
  handler,
};

This code defines a generic cloud function handler and a function makeHandler to create specialized handlers from code snippets.

Here's a breakdown:

  1. Imports:

  2. handler Function:

  3. makeHandler Function: